package librpc

import (
	"fmt"
	"net"
	"kumachan/standalone/rx"
	"kumachan/standalone/rpc"
	"kumachan/interpreter/runtime/rt"
)


type Value = rt.Value

func Access (
	id        rpc.ServiceIdentifier,
	api       rt.RpcApi,
	backend   ClientBackend,
	options   ClientOptions,
	argument  Value,
	consumer  func(Value)(rx.Observable),
) rx.Observable {
	return rx.NewSync(func() (rx.Object, bool) {
		var conn, err = backend.Access()
		if err != nil { return err, false }
		return conn, true
	}).Then(func(conn_ rx.Object) rx.Observable {
		var conn = conn_.(net.Conn)
		var service, ok = api.LookupService(id)
		if !(ok) { panic(fmt.Sprintf("service %s does not exist", id)) }
		var wrapped_consumer = func(instance *rpc.ClientInstance) rx.Observable {
			var adapted_instance = &rt.ValInterface {
				ConcreteValue: instance,
				DispatchTable: service.DispatchTable,
			}
			return consumer(adapted_instance)
		}
		var full_options = &rpc.ClientOptions {
			Connection:          conn,
			DebugOutput:         options.GetDebugOutput(),
			ConstructorArgument: argument,
			InstanceConsumer:    wrapped_consumer,
			Limits:              options.Limits,
			StreamVdApi:         api,
		}
		return rpc.Client(service.Interface, full_options)
	})
}

func Serve (
	id           rpc.ServiceIdentifier,
	api          rt.RpcApi,
	backend      ServerBackend,
	options      ServerOptions,
	constructor  rpc.ServiceConstructorImpl,
) rx.Observable {
	return rx.NewSync(func() (rx.Object, bool) {
		var l, err = backend.Serve()
		if err != nil { return err, false }
		return l, true
	}).Then(func(l_ rx.Object) rx.Observable {
		var l = l_.(net.Listener)
		var service, ok = api.LookupService(id)
		if !(ok) { panic(fmt.Sprintf("service %s does not exist", id)) }
		var interface_ = service.Interface
		var methods = service.MethodsImpl
		var service_impl = implementService(interface_, constructor, methods)
		var full_options = &rpc.ServerOptions {
			Listener:     l,
			DebugOutput:  options.GetDebugOutput(),
			Limits:       options.Limits,
			StreamVdApi:  api,
		}
		return rpc.Server(service_impl, full_options)
	})
}

func implementService (
	interface_     rpc.ServiceInterface,
	constructor    rpc.ServiceConstructorImpl,
	method_values  map[string] rpc.ServiceMethodImpl,
) rpc.Service {
	var methods = make(map[string] rpc.ServiceMethod)
	for name, method_info := range interface_.Methods {
		var value, exists = method_values[name]
		if !(exists) { panic("something went wrong") }
		methods[name] = rpc.ServiceMethod {
			ServiceMethodInterface: method_info,
			Impl: value,
		}
	}
	var service = rpc.Service {
		Identifier:  interface_.Identifier,
		Constructor: rpc.ServiceConstructor {
			ServiceConstructorInterface: interface_.Constructor,
			Impl: constructor,
		},
		Methods: methods,
	}
	return service
}


